{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 飞桨深度学习平台设计之“道”\n",
    "\n",
    "当读者习惯使用飞桨框架后会发现程序呈现出“八股文”的形态，即不同的程序员、使用不同模型、解决不同任务的时候，他们编写的建模程序是极其相似的。虽然这些设计在某些“极客”的眼里缺乏精彩，但从实用性的角度，我们更期望建模者聚焦需要解决的任务，而不是将精力投入在框架的学习上。因此使用飞桨编写模型是有标准的套路设计的，只要通过一个示例程序掌握使用飞桨的方法，编写不同任务的多种建模程序将变得十分容易。\n",
    "\n",
    "这点与Python的设计思想一致：对于某个特定功能，并不是实现方式越灵活、越多样越好，最好只有一种符合“道”的最佳实现。此处“道”指的是如何更加匹配人的思维习惯。当程序员第一次看到Python的多种应用方式时，感觉程序天然就应该如此实现。但相信我，不是所有的编程语言都具备这样合“道”的设计，很多编程语言的设计思路是人需要去理解机器的运作原理，而不能以人类习惯的方式设计程序。同时，灵活意味着复杂，会增加程序员之间的沟通难度，也不适合现代工业化生产软件的趋势。\n",
    "\n",
    "飞桨设计的初衷不仅要易于学习，还期望使用者能够体会到它的美感和哲学，与人类最自然的认知和使用习惯契合。\n",
    "\n",
    "# 使用飞桨构建波士顿房价预测模型\n",
    "\n",
    "本书中的案例覆盖预测任务、推荐系统、计算机视觉和自然语言处理等主流应用场景，所有案例的代码结构完全一致，如 **图1** 所示。\n",
    "<br></br>\n",
    "<center><img src=\"https://ai-studio-static-online.cdn.bcebos.com/1ffe7bba8205457db1f3df637686c961403eaa84bfec4ebf822cefdea2e4a001\" width=\"800\" hegiht=\"\" ></center>\n",
    "<center><br>图1：使用飞桨框架构建神经网络过程</br></center>\n",
    "<br></br>\n",
    "\n",
    "在之前的章节中，我们学习了使用Python和Numpy构建波士顿房价预测模型的方法，本节课我们将尝试使用飞桨重写房价预测模型，大家可以体会一下二者的异同。在数据处理之前，需要先加载飞桨框架的相关类库。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#加载飞桨、Numpy和相关类库\n",
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "import paddle.fluid.dygraph as dygraph\n",
    "from paddle.fluid.dygraph import Linear\n",
    "import numpy as np\n",
    "import os\n",
    "import random"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "代码中参数含义如下：\n",
    "\n",
    "* paddle/fluid：飞桨的主库，目前大部分的实用函数均在paddle.fluid包内。\n",
    "* dygraph：动态图的类库。\n",
    "* FC：神经网络的全连接层函数，即包含所有输入权重相加和激活函数的基本神经元结构。在房价预测任务中，使用只有一层的神经网络（全连接层）来实现线性回归模型。\n",
    "<br></br>\n",
    "\n",
    "------\n",
    "\n",
    "**说明：**\n",
    "\n",
    "飞桨支持两种深度学习建模编写方式，更方便调试的动态图模式和性能更好并便于部署的静态图模式。\n",
    "\n",
    "* 静态图模式（声明式编程范式，类比C++）：先编译后执行的方式。用户需预先定义完整的网络结构，再对网络结构进行编译优化后，才能执行获得计算结果。\n",
    "* 动态图模式（命令式编程范式，类比Python）：解析式的执行方式。用户无需预先定义完整的网络结构，每写一行网络代码，即可同时获得计算结果。\n",
    "\n",
    "为了学习模型和调试的方便，本教程均使用动态图模式编写模型。在后续的资深教程中，会详细介绍静态图以及将动态图模型转成静态图的方法。仅在部分场景下需要模型转换，并且是相对容易的。\n",
    "\n",
    "------\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 数据处理\n",
    "\n",
    "数据处理的代码不依赖框架实现，与使用Python构建房价预测任务的代码相同（[详细解读请参考1-2章](http://aistudio.baidu.com/aistudio/projectdetail/206887)），这里不再赘述。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def load_data():\n",
    "    # 从文件导入数据\n",
    "    datafile = './work/housing.data'\n",
    "    data = np.fromfile(datafile, sep=' ')\n",
    "\n",
    "    # 每条数据包括14项，其中前面13项是影响因素，第14项是相应的房屋价格中位数\n",
    "    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', \\\n",
    "                      'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]\n",
    "    feature_num = len(feature_names)\n",
    "\n",
    "    # 将原始数据进行Reshape，变成[N, 14]这样的形状\n",
    "    data = data.reshape([data.shape[0] // feature_num, feature_num])\n",
    "\n",
    "    # 将原数据集拆分成训练集和测试集\n",
    "    # 这里使用80%的数据做训练，20%的数据做测试\n",
    "    # 测试集和训练集必须是没有交集的\n",
    "    ratio = 0.8\n",
    "    offset = int(data.shape[0] * ratio)\n",
    "    training_data = data[:offset]\n",
    "\n",
    "    # 计算train数据集的最大值，最小值，平均值\n",
    "    maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0), \\\n",
    "                                 training_data.sum(axis=0) / training_data.shape[0]\n",
    "    \n",
    "    # 记录数据的归一化参数，在预测时对数据做归一化\n",
    "    global max_values\n",
    "    global min_values\n",
    "    global avg_values\n",
    "    max_values = maximums\n",
    "    min_values = minimums\n",
    "    avg_values = avgs\n",
    "\n",
    "    # 对数据进行归一化处理\n",
    "    for i in range(feature_num):\n",
    "        #print(maximums[i], minimums[i], avgs[i])\n",
    "        data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])\n",
    "\n",
    "    # 训练集和测试集的划分比例\n",
    "    #ratio = 0.8\n",
    "    #offset = int(data.shape[0] * ratio)\n",
    "    training_data = data[:offset]\n",
    "    test_data = data[offset:]\n",
    "    return training_data, test_data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 模型设计\n",
    "\n",
    "模型定义的实质是定义线性回归的网络结构，飞桨建议通过创建Python类的方式完成模型网络的定义，即定义``init``函数和``forward``函数。``forward``函数是框架指定实现前向计算逻辑的函数，程序在调用模型实例时会自动执行forward方法。在``forward``函数中使用的网络层需要在``init``函数中声明。\n",
    "\n",
    "实现过程分如下两步：\n",
    "\n",
    "1. **定义init函数**：在类的初始化函数中声明每一层网络的实现函数。在房价预测模型中，只需要定义一层全连接层FC，模型结构和[1-2 节](https://aistudio.baidu.com/aistudio/projectdetail/206887)模型保持一致。\n",
    "1. **定义forward函数**：构建神经网络结构，实现前向计算过程，并返回预测结果，在本任务中返回的是房价预测结果。\n",
    "\n",
    "------\n",
    "**说明：**\n",
    "\n",
    "``name_scope``变量用于调试模型时追踪多个模型的变量，在此忽略即可，飞桨1.7及之后版本不强制用户设置``name_scope``。\n",
    "\n",
    "------"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class Regressor(fluid.dygraph.Layer):\n",
    "    def __init__(self, name_scope):\n",
    "        super(Regressor, self).__init__(name_scope)\n",
    "        name_scope = self.full_name()\n",
    "        # 定义一层全连接层，输出维度是1，激活函数为None，即不使用激活函数\n",
    "        self.fc = Linear(input_dim=13, output_dim=1, act=None)\n",
    "    \n",
    "    # 网络的前向计算函数\n",
    "    def forward(self, inputs):\n",
    "        x = self.fc(inputs)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 训练配置\n",
    "\n",
    "训练配置过程包含四步，如 **图2** 所示：\n",
    "<center><img src=\"https://ai-studio-static-online.cdn.bcebos.com/d3811185a5f646b49c398a9fdc8be3c72794b9b23d9e4d71af92361e0bb5e608\" width=\"700\" hegiht=\"\" ></center>\n",
    "<center><br>图2：训练配置流程示意图</br></center>\n",
    "<br></br>\n",
    "\n",
    "1. 以``grard``函数指定运行训练的机器资源，表明在``with``作用域下的程序均执行在本机的CPU资源上。``dygraph.guard``表示在``with``作用域下的程序会以飞桨动态图的模式执行（实时执行）。\n",
    "1. 声明定义好的回归模型Regressor实例，并将模型的状态设置为训练。\n",
    "1. 使用load_data函数加载训练数据和测试数据。\n",
    "1. 设置优化算法和学习率，优化算法采用随机梯度下降[SGD](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/optimizer_cn/SGDOptimizer_cn.html#cn-api-fluid-optimizer-sgdoptimizer)，学习率设置为0.01。\n",
    "\n",
    "训练配置代码如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 定义飞桨动态图的工作环境\n",
    "with fluid.dygraph.guard():\n",
    "    # 声明定义好的线性回归模型\n",
    "    model = Regressor(\"Regressor\")\n",
    "    # 开启模型训练模式\n",
    "    model.train()\n",
    "    # 加载数据\n",
    "    training_data, test_data = load_data()\n",
    "    # 定义优化算法，这里使用随机梯度下降-SGD\n",
    "    # 学习率设置为0.01\n",
    "    opt = fluid.optimizer.SGD(learning_rate=0.01, parameter_list=model.parameters())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "------\n",
    "\n",
    "**说明：**\n",
    "\n",
    "1. 默认本案例运行在读者的笔记本上，因此模型训练的机器资源为CPU。\n",
    "1. 模型实例有两种状态：训练状态``（.train()）``和预测状态``(.eval())``。训练时要执行正向计算和反向传播梯度两个过程，而预测时只需要执行正向计算。为模型指定运行状态，有两点原因：\n",
    "\n",
    "（1）部分高级的算子（例如Drop out和Batch Normalization，在计算机视觉的章节会详细介绍）在两个状态执行的逻辑不同。\n",
    "\n",
    "（2）从性能和存储空间的考虑，预测状态时更节省内存，性能更好。\n",
    "\n",
    "3. 在上述代码中可以发现声明模型、定义优化器等操作都在``with``创建的 [fluid.dygraph.guard()](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn/guard_cn.html#guard)上下文环境中进行，可以理解为``with fluid.dygraph.guard()``创建了飞桨动态图的工作环境，在该环境下完成模型声明、数据转换及模型训练等操作。\n",
    "------\n",
    "\n",
    "在基于Python实现神经网络模型的案例中，我们为实现梯度下降编写了大量代码，而使用飞桨框架只需要定义SDG就可以实现优化器设置，大大简化了这个过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 训练过程\n",
    "\n",
    "训练过程采用二层循环嵌套方式：\n",
    "\n",
    "- **内层循环：** 负责整个数据集的一次遍历，采用分批次方式（batch）。假设数据集样本数量为1000，一个批次有10个样本，则遍历一次数据集的批次数量是1000/10=100，即内层循环需要执行100次。\n",
    "\n",
    "        for iter_id, mini_batch in enumerate(mini_batches):\n",
    "\n",
    "- **外层循环：** 定义遍历数据集的次数，通过参数EPOCH_NUM设置。\n",
    "\n",
    "        for epoch_id in range(EPOCH_NUM):\n",
    "\n",
    "------\n",
    "**说明**:\n",
    "\n",
    "batch的取值会影响模型训练效果。batch过大，会增大内存消耗和计算时间，且效果并不会明显提升；batch过小，每个batch的样本数据将没有统计意义。由于房价预测模型的训练数据集较小，我们将batch为设置10。\n",
    "\n",
    "------\n",
    "\n",
    "每次内层循环都需要执行如下四个步骤，如 **图3** 所示，计算过程与使用Python编写模型完全一致。\n",
    "<center><img src=\"https://ai-studio-static-online.cdn.bcebos.com/b1dfc9ec1495486b9fdbecb02ad25a1d0fe64fcf847d4df8839a55c41684ef33\" width=\"700\" hegiht=\"\" ></center>\n",
    "<center><br>图3：内循环计算过程</br></center>\n",
    "<br></br>\n",
    "\n",
    "1. 数据准备：将一个批次的数据转变成np.array和内置格式。\n",
    "1. 前向计算：将一个批次的样本数据灌入网络中，计算输出结果。\n",
    "1. 计算损失函数：以前向计算结果和真实房价作为输入，通过损失函数``square_error_cost``计算出损失函数值（Loss），API可参考[square_error_cost](https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/layers_cn/square_error_cost_cn.html#square-error-cost/)。飞桨所有的API接口都有完整的说明和使用案例，在后续的资深教程中我们会详细介绍API的查阅方法。\n",
    "1. 反向传播：执行梯度反向传播``backward``函数，即从后到前逐层计算每一层的梯度，并根据设置的优化算法更新参数``opt.minimize``。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 0, iter: 0, loss is: [0.09231653]\n",
      "epoch: 0, iter: 20, loss is: [0.05111608]\n",
      "epoch: 0, iter: 40, loss is: [0.04298488]\n",
      "epoch: 1, iter: 0, loss is: [0.05798328]\n",
      "epoch: 1, iter: 20, loss is: [0.04172831]\n",
      "epoch: 1, iter: 40, loss is: [0.06064801]\n",
      "epoch: 2, iter: 0, loss is: [0.06820373]\n",
      "epoch: 2, iter: 20, loss is: [0.04361733]\n",
      "epoch: 2, iter: 40, loss is: [0.11125734]\n",
      "epoch: 3, iter: 0, loss is: [0.01690602]\n",
      "epoch: 3, iter: 20, loss is: [0.04361285]\n",
      "epoch: 3, iter: 40, loss is: [0.03345194]\n",
      "epoch: 4, iter: 0, loss is: [0.04600745]\n",
      "epoch: 4, iter: 20, loss is: [0.01972655]\n",
      "epoch: 4, iter: 40, loss is: [0.00918559]\n",
      "epoch: 5, iter: 0, loss is: [0.02758535]\n",
      "epoch: 5, iter: 20, loss is: [0.07461586]\n",
      "epoch: 5, iter: 40, loss is: [0.00966942]\n",
      "epoch: 6, iter: 0, loss is: [0.01973187]\n",
      "epoch: 6, iter: 20, loss is: [0.00888905]\n",
      "epoch: 6, iter: 40, loss is: [0.01367587]\n",
      "epoch: 7, iter: 0, loss is: [0.10398467]\n",
      "epoch: 7, iter: 20, loss is: [0.02348464]\n",
      "epoch: 7, iter: 40, loss is: [0.02636003]\n",
      "epoch: 8, iter: 0, loss is: [0.05337438]\n",
      "epoch: 8, iter: 20, loss is: [0.03853562]\n",
      "epoch: 8, iter: 40, loss is: [0.03452815]\n",
      "epoch: 9, iter: 0, loss is: [0.03540625]\n",
      "epoch: 9, iter: 20, loss is: [0.01843123]\n",
      "epoch: 9, iter: 40, loss is: [0.00875628]\n"
     ]
    }
   ],
   "source": [
    "with dygraph.guard(fluid.CPUPlace()):\n",
    "    EPOCH_NUM = 10   # 设置外层循环次数\n",
    "    BATCH_SIZE = 10  # 设置batch大小\n",
    "    \n",
    "    # 定义外层循环\n",
    "    for epoch_id in range(EPOCH_NUM):\n",
    "        # 在每轮迭代开始之前，将训练数据的顺序随机的打乱\n",
    "        np.random.shuffle(training_data)\n",
    "        # 将训练数据进行拆分，每个batch包含10条数据\n",
    "        mini_batches = [training_data[k:k+BATCH_SIZE] for k in range(0, len(training_data), BATCH_SIZE)]\n",
    "        # 定义内层循环\n",
    "        for iter_id, mini_batch in enumerate(mini_batches):\n",
    "            x = np.array(mini_batch[:, :-1]).astype('float32') # 获得当前批次训练数据\n",
    "            y = np.array(mini_batch[:, -1:]).astype('float32') # 获得当前批次训练标签（真实房价）\n",
    "            # 将numpy数据转为飞桨动态图variable形式\n",
    "            house_features = dygraph.to_variable(x)\n",
    "            prices = dygraph.to_variable(y)\n",
    "            \n",
    "            # 前向计算\n",
    "            predicts = model(house_features)\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.square_error_cost(predicts, label=prices)\n",
    "            avg_loss = fluid.layers.mean(loss)\n",
    "            if iter_id%20==0:\n",
    "                print(\"epoch: {}, iter: {}, loss is: {}\".format(epoch_id, iter_id, avg_loss.numpy()))\n",
    "            \n",
    "            # 反向传播\n",
    "            avg_loss.backward()\n",
    "            # 最小化loss,更新参数\n",
    "            opt.minimize(avg_loss)\n",
    "            # 清除梯度\n",
    "            model.clear_gradients()\n",
    "    # 保存模型\n",
    "    fluid.save_dygraph(model.state_dict(), 'LR_model')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "这个实现过程令人惊喜，前向计算、计算损失和反向传播梯度，每个操作居然只有1-2行代码即可实现！我们再也不用一点点的实现模型训练的细节，这就是使用飞桨框架的威力！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## 5. 保存并测试模型\n",
    "\n",
    "### 保存模型\n",
    "\n",
    "将模型当前的参数数据``model.state_dict()``保存到文件中（通过参数指定保存的文件名 LR_model），以备预测或校验的程序调用，代码如下所示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模型保存成功，模型参数保存在LR_model中\n"
     ]
    }
   ],
   "source": [
    "# 定义飞桨动态图工作环境\n",
    "with fluid.dygraph.guard():\n",
    "    # 保存模型参数，文件名为LR_model\n",
    "    fluid.save_dygraph(model.state_dict(), 'LR_model')\n",
    "    print(\"模型保存成功，模型参数保存在LR_model中\")\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "理论而言，直接使用模型实例即可完成预测，而本教程中预测的方式为什么是先保存模型，再加载模型呢？这是因为在实际应用中，训练模型和使用模型往往是不同的场景。模型训练通常使用大量的线下服务器（不对外向企业的客户/用户提供在线服务），而模型预测则通常使用线上提供预测服务的服务器，或者将已经完成的预测模型嵌入手机或其他终端设备中使用。因此本教程的讲解方式更贴合真实场景的使用方法。\n",
    "\n",
    "回顾下基于飞桨实现的房价预测模型，实现效果与之前基于Python实现的模型没有区别，但两者的实现成本有天壤之别。飞桨的愿景是用户只需要了解模型的逻辑概念，不需要关心实现细节，就能搭建强大的模型。\n",
    "\n",
    "### 测试模型\n",
    "\n",
    "下面我们选择一条数据样本，测试下模型的预测效果。测试过程和在应用场景中使用模型的过程一致，主要可分成如下三个步骤：\n",
    "\n",
    "1. 配置模型预测的机器资源。本案例默认使用本机，因此无需写代码指定。\n",
    "1. 将训练好的模型参数加载到模型实例中。由两个语句完成，第一句是从文件中读取模型参数；第二句是将参数内容加载到模型。加载完毕后，需要将模型的状态调整为``evalueation``（校验）。上文中提到，训练状态的模型需要同时支持前向计算和反向传导梯度，模型的实现较为臃肿，而校验和预测状态的模型只需要支持前向计算，模型的实现更加简单，性能更好。\n",
    "1. 将待预测的样本特征输入到模型中，打印输出的预测结果。\n",
    "\n",
    "通过``load_one_example``函数实现从数据集中抽一条样本作为测试样本，具体实现代码如下所示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def load_one_example(data_dir):\n",
    "    f = open(data_dir, 'r')\n",
    "    datas = f.readlines()\n",
    "    # 选择倒数第10条数据用于测试\n",
    "    tmp = datas[-10]\n",
    "    tmp = tmp.strip().split()\n",
    "    one_data = [float(v) for v in tmp]\n",
    "\n",
    "    # 对数据进行归一化处理\n",
    "    for i in range(len(one_data)-1):\n",
    "        one_data[i] = (one_data[i] - avg_values[i]) / (max_values[i] - min_values[i])\n",
    "\n",
    "    data = np.reshape(np.array(one_data[:-1]), [1, -1]).astype(np.float32)\n",
    "    label = one_data[-1]\n",
    "    return data, label"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Inference result is [[12.680977]], the corresponding label is 19.7\n"
     ]
    }
   ],
   "source": [
    "\n",
    "with dygraph.guard():\n",
    "    # 参数为保存模型参数的文件地址\n",
    "    model_dict, _ = fluid.load_dygraph('LR_model')\n",
    "    model.load_dict(model_dict)\n",
    "    model.eval()\n",
    "\n",
    "    # 参数为数据集的文件地址\n",
    "    test_data, label = load_one_example('./work/housing.data')\n",
    "    # 将数据转为动态图的variable格式\n",
    "    test_data = dygraph.to_variable(test_data)\n",
    "    results = model(test_data)\n",
    "\n",
    "    # 对结果做反归一化处理\n",
    "    results = results * (max_values[-1] - min_values[-1]) + avg_values[-1]\n",
    "    print(\"Inference result is {}, the corresponding label is {}\".format(results.numpy(), label))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "通过比较“模型预测值”和“真实房价”可见，模型的预测效果与真实房价接近。房价预测仅是一个最简单的模型，使用飞桨编写均可事半功倍。那么对于工业实践中更复杂的模型，使用飞桨节约的成本是不可估量的。同时飞桨针对很多应用场景和机器资源做了性能优化，在功能和性能上远强于自行编写的模型。\n",
    "\n",
    "从下一章开始，我们就将通过“手写数字识别”的案例，完整的掌握使用飞桨编写模型的方方面面。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 作业1-6\n",
    "\n",
    "1. 在[AI studio](http://aistudio.baidu.com/)上阅读房价预测案例（两个版本）的代码，并运行观察效果。\n",
    "1. 在本机或服务器上安装Python、jupyter和Paddle，运行房价预测的案例（两个版本），并观察运行效果。\n",
    "1. 想一想：基于Python编写的模型和基于飞桨编写的模型在存在哪些异同？如程序结构，编写难易度，模型的预测效果，训练的耗时等等。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 1.7.0 (Python 2.7)",
   "language": "python",
   "name": "py27-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
